package org.diy.traffic.sinks;

import java.util.Iterator;
import java.util.List;

import org.diy.traffic.updates.TrafficUpdate;


/**
 * This class represents an object which functions as a consumer of traffic updates.
 * This class cannot itself be instantiated, it must be subclassed.
 * @author Dan Greenblatt
 *
 */
public abstract class TrafficSink {
	
	/** A string which represents the name of this sink. Subclasses should set this to something unique
	 * on a per-instance basis, by appending a unique instance name to a subclass base type string.
	 */
	protected String sinkName = "";
	
	/** A time in milliseconds when this sink last processed an update. Used for tracking purposes **/
	protected long timeLastUpdateProcessed  = 0;
	
	/** A value representing how many updates this sink handled in the past minute **/
	private int numUpdatesThisMinute   = 0;
	
	/** A default value representing the default number of updates a sink can process per minute **/	
	protected final static int DEFAULT_UPDATES_PER_MINUTE = 5;
	
	/** A value representing the maximum number of updates this sink should be allowed to handle per minute **/
	private int maxUpdatesPerMinute    = DEFAULT_UPDATES_PER_MINUTE;
	
	/**
	 * Constructor for a traffic sink
	 * @param sinkName A unique name to by which this TrafficSink instance can be represented.
	 * @param updateLimit A number which represents the max number of times per minute this sink should be allowed to be updated
	 */
	public TrafficSink(String sinkName, int updateLimit) {
		this.sinkName = sinkName;
		this.maxUpdatesPerMinute = updateLimit;
	}
	
	/**
	 * Basic constructor for a traffic sink. Uses the default number of updates per minute
	 * @param sinkName A unique name to by which this TrafficSink instance can be represented.
	 */
	public TrafficSink(String sinkName) {
		this(sinkName, DEFAULT_UPDATES_PER_MINUTE);
	}
	
	
	/**
	 * A utility function which subclasses should call to register that the sink has handled an update
	 */
	public void didHandleUpdate() {
		// increase the number of updates I've handled this minute
		numUpdatesThisMinute++;
		
		// set my last update to now.
		timeLastUpdateProcessed = System.currentTimeMillis();
	}

	/**
	 * This function can be used by subclasses to determine whether this sink is within the pre-determined rate limit.
	 * I.e., it answers the question "If I handle another update, will I be within the rate limit?"
	 * @return true if posting another update will be within pre-determined rate limit, false if not
	 * 
	 * TODO Take another look at the logic on this one ... seems hinky.
	 */
	public boolean isWithinRateLimit() {
		
		// if it's been more than a minute since I processed my last update
		if ( (System.currentTimeMillis()-timeLastUpdateProcessed) >= 60000) {
			//TLog.log(Level.FINEST, "1m lapsed, resetting");
			// I can reset the num updates this minute to zero, since it's been more than a minute since I last updated
			numUpdatesThisMinute = 0;
		}
		
		//return true if the total number of updates + 1 is under (or equal) to the rate limit
		return (numUpdatesThisMinute+1 <= maxUpdatesPerMinute);		
	}
	
	
	/**
	 * This function must be implemented by subclasses. It is supposed to do whatever is necessary to handle an
	 * TrafficUpdate instance taken off the ATC's update queue. Note that by the time the update gets to this sink,
	 * it will already have been checked to make sure that it is routed to here (i.e. a TrafficSink shouldn't get
	 * a TrafficUpdate it doesn't know how to handle, unless it was set up that way).
	 * @param update the update to process
	 * @return true if the update was processed, false if it wasn't (e.g. if the sink's rate limit has been reached)
	 * @throws TrafficSinkException if something goes wrong while performing the duties of handling a TrafficUpdate
	 */
	public abstract boolean processUpdate(TrafficUpdate update) throws TrafficSinkException;
	
	/**
	 * A utility function which processes a list of TrafficUpdates. It just iterates over the list and calls
	 * processUpdate on each.
	 * @param updates a list of TrafficUpdates to process.
	 * @return the number of TrafficUpdates which were actually processed
	 * @throws TrafficSinkException if something goes wrong while processing the updates
	 */
	public int processUpdates(List<TrafficUpdate> updates) throws TrafficSinkException {
		Iterator<TrafficUpdate> updateI = updates.iterator();
		int numUpdatesHandled = 0;
		while (updateI.hasNext()) {
			TrafficUpdate update = updateI.next();
			if (processUpdate(update))
				numUpdatesHandled++;
		}
		return numUpdatesHandled;
	}
	
	/**
	 * A unique string identifier for this TrafficSink
	 * @return A string representing a unique identifier for this TrafficSink
	 */
	public String getName() {
		return sinkName;
	}
	
	/**
	 * Utility function which makes the sets the String representation of this TrafficSink to be its unique name
	 */
	@Override
	public String toString() {
		return getName();
	}
	
	/**
	 * A class which represents an exceptional state arising as a result of processing TrafficUpdates.
	 * @author Dan Greenblatt
	 *
	 */
	public class TrafficSinkException extends Exception {
		
		public TrafficSinkException() {super();}
		
		public TrafficSinkException(Exception e) {super(e);}
		
		public TrafficSinkException(String s) {super(s);}
	}
}
